// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: jasonyoon

#pragma once

#include <fmt/printf.h>

#include <algorithm>
#include <memory>
#include <vector>
#include <map>
#include <string>
#include <utility>
#include <unordered_map>
#include <unordered_set>

#include "presto/quant/math/moving_average.h"
#include "presto/quant/math/sampled_moving_average.h"
#include "presto/quant/math/sampled_moving_average2.h"

#include "coin/proto/coin_executor.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin/proto/coin_telemetry.pb.h"

#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"

#include "coin2/strategy/linear_model/logic.h"
#include "coin2/strategy/linear_model/util.h"
#include "coin2/strategy/linear_model/mm_util.h"
#include "coin2/strategy/linear_model/mm_util3.h"
#include "coin2/strategy/linear_model/product_strategy.h"
#include "coin2/strategy/linear_model/working_order_manager.h"
#include "coin2/strategy/order_executor/executor.h"


namespace coin2::strategy::linear_model {

using ::coin2::exchange::base::symbology::ProductEncyclopedia;
using ::coin2::exchange::base::order::OrderUpdate;
using ::coin2::strategy::order_executor::OrderExecutorSystem;
using presto::math::TimeWindowMovingAverage;
using presto::math::SampledMovingAverage;
using presto::math::SampledMovingAverage2;
using presto::math::MeanOnline;

enum ColumnFromLinearModel {
  MEAN_SCALE,
  STD,
  REF_ASKP,
  REF_BIDP,
  DEBUG_MIDP,
  TARGET_POSITION,
  TIME_REMAINING,
  NUM_COLUMNS,
};

struct AggregatedSizer2 {
  explicit AggregatedSizer2(const LmFocusMidfreqConfig& focus_midfreq_config)
      : focus_midfreq_config(focus_midfreq_config) {}

  void Init(
       int64_t curr_time,
       const MarketExchangeApi& mea,
       const std::vector<const Symbol*>& symbols) {
    for (const auto& prod_clear : focus_midfreq_config.symbols_clear()) {
      auto product = CreateProductFromNormString(mea, prod_clear, curr_time);
      for (const Symbol* symbol : symbols) {
        if (StringEndsWith(symbol->symbol_string(), product->relative_norm())) {
          LOG(INFO) << "Clear: " << product->relative_norm();
          symbols_clear.insert(symbol);
        }
      }
    }

    int ind = 0;
    for (const Symbol* symbol : symbols) {
      hedge_index[symbol] = ind;
      hedge_info.push_back(0);
      ind++;
    }

    for (const auto& hedge_symbol : focus_midfreq_config.hedge_symbols()) {
      auto product = CreateProductFromNormString(mea, hedge_symbol, curr_time);
      for (const Symbol* symbol : symbols) {
        if (StringEndsWith(symbol->symbol_string(), product->relative_norm())) {
          ind = hedge_index[symbol];
          hedge_info[ind] = 1;
          hedge_symbol_cnt++;
          DLOG(INFO) << "hedge ind: "<< ind;
        }
      }
    }
    LOG(INFO) << "hedge_symbol_cnt: "<< hedge_symbol_cnt;
  }

  void UpdatePosition(const Symbol* symbol, double curr_pos_quote) {
    if ((hedge_symbol_cnt == 0) || !IsHedgeSymbol(symbol)) {
      double prev_pos_quote = pos_quote[symbol];
      pos_quote[symbol] = curr_pos_quote;
      pos_quote_sum += curr_pos_quote - prev_pos_quote;
      pos_quote_abssum += (std::abs(curr_pos_quote) - std::abs(prev_pos_quote));
    } else {
      double prev_pos_quote = hedge_pos_quote[symbol];
      hedge_pos_quote[symbol] = curr_pos_quote;
      hedge_pos_quote_sum += curr_pos_quote - prev_pos_quote;
    }
  }

  double GetHedgeSignal(const Symbol* symbol, double gmv, double pos_diff_threshold) {
    if (hedge_symbol_cnt == 0) {
      return 0;
    }

    if (IsHedgeSymbol(symbol)) {
      double desired_hedge_pos = pos_quote_sum / hedge_symbol_cnt;
      double pos_diff_thres_size = gmv * pos_diff_threshold;

      // signal need to be opposite direction to exposure
      double signal = -(hedge_pos_quote[symbol] + desired_hedge_pos) / pos_diff_thres_size;
      return signal;
    } else {
      return 0;
    }
  }

  int IsHedgeSymbol(const Symbol* symbol) {
    if (hedge_symbol_cnt == 0) {
      return 0;
    }
    int ind = hedge_index[symbol];
    return hedge_info[ind];
  }

  int GetHedgeSymbolCnt() {
    return hedge_symbol_cnt;
  }

  std::pair<double, double> GetRemainingBuySellQuote() {
    double remaining_buy = focus_midfreq_config.max_pos_quote() - pos_quote_sum;
    double remaining_sell = pos_quote_sum - focus_midfreq_config.min_pos_quote();
    return std::make_pair(remaining_buy, remaining_sell);
  }

  std::pair<double, double> GetRemainingAbsBuySellQuote(const Symbol* symbol) {
    double max_quote = focus_midfreq_config.max_abspos_quote();
    max_quote = std::max(max_quote, pos_quote_abssum);
    double curr_pos_quote = pos_quote[symbol];
    double remaining_buy = 0, remaining_sell = 0;
    if (curr_pos_quote > 0) {
      remaining_buy = max_quote - pos_quote_abssum;
      remaining_sell = std::max(curr_pos_quote,
          max_quote - pos_quote_abssum + 2 * curr_pos_quote);
    } else {
      remaining_buy = std::max(-curr_pos_quote,
          max_quote - pos_quote_abssum - 2 * curr_pos_quote);
      remaining_sell = max_quote - pos_quote_abssum;
    }
    return std::make_pair(remaining_buy, remaining_sell);
  }

  double GetAggregatedNormalizedPos() const {
    return pos_quote_sum / (
        focus_midfreq_config.max_pos_quote() - focus_midfreq_config.min_pos_quote());
  }

  double GetAggregatedPosQuote() const {
    return pos_quote_sum;
  }

  LmFocusMidfreqConfig focus_midfreq_config;
  double pos_quote_sum = 0, pos_quote_abssum = 0;
  double hedge_pos_quote_sum = 0;
  std::unordered_map<const Symbol*, double> pos_quote;
  std::unordered_set<const Symbol*> symbols_clear;
  std::unordered_map<const Symbol*, double> hedge_pos_quote;
  presto::detail::FastSymbolMap<int> hedge_index;
  std::vector<int> hedge_info;
  int hedge_symbol_cnt = 0;
};


struct BlockInfo {
  double lot_size = 0, min_pos = 0, max_pos = 0;
  bool buy_block = false, sell_block = false;
  double aggr_skew_bps = 0, min_order_qty = 0;
  double remain_buy_qty = 0, remain_sell_qty = 0;
  double curr_pos = 0;
  double pos_quote_sum = 0, hedge_pos_quote_sum = 0;
  double hedge_signal = 0; bool is_hedge_symbol;
};

typedef SampledMovingAverage<double> sma;
typedef SampledMovingAverage2<double> sma2;
typedef TimeWindowMovingAverage<double> twma;

struct LmSubStrategy2 {
  LmSubStrategy2(
      const MarketExchangeApi& mea,
      const IProduct* product,
      const Symbol* symbol,
      const LmFocusMidfreqConfig& lm_focus_midfreq_config,
      ProductEncyclopedia* product_cache)
      : mea(mea),
        symbol(symbol),
        focus_midfreq_config(lm_focus_midfreq_config),
        lholder_(mea, *product, symbol, product_cache) {
    market_turnover_ = new sma(focus_midfreq_config.pr_window_in_sec() * 1'000'000'000LL,
        1'000'000'000LL);

    if (focus_midfreq_config.liquidity_constraint() > 0) {
      liquidity_ = new sma(3600LL * 24LL * 1'000'000'000LL, 60'000'000'000LL);
      use_liq_const_ = true;
    }

    if (focus_midfreq_config.volat_window() > 0 &&
        (focus_midfreq_config.volat_const() > 0 || focus_midfreq_config.volatility_const() > 0)) {
      tradep_ = new sma(
          focus_midfreq_config.volat_window() * 1'000'000'000LL,
          1'000'000'000LL);
      tradep2_ = new sma(
          focus_midfreq_config.volat_window() * 1'000'000'000LL,
          1'000'000'000LL);
      calc_volatility_ = true;
    }

    if (focus_midfreq_config.spread_window() > 0 && focus_midfreq_config.spread_const() > 0) {
      spread_ = new sma(
          focus_midfreq_config.spread_window() * 1'000'000'000LL,
          1'000'000'000LL);
      calc_spread_ = true;
    }

    buy_turnover_.ResizeWindow(focus_midfreq_config.pr_window_in_sec() * 1'000'000'000LL);
    sell_turnover_.ResizeWindow(focus_midfreq_config.pr_window_in_sec() * 1'000'000'000LL);
    if (focus_midfreq_config.fill_cooldown_window()) {
      buy_filled_.ResizeWindow(focus_midfreq_config.fill_cooldown_window() * 1'000'000'000LL);
      sell_filled_.ResizeWindow(focus_midfreq_config.fill_cooldown_window() * 1'000'000'000LL);
      use_fill_cooldown_ = true;
    }
    ask_bbo_ = new twma(focus_midfreq_config.book_window_in_sec() * 1'000'000'000LL);
    bid_bbo_ = new twma(focus_midfreq_config.book_window_in_sec() * 1'000'000'000LL);
    if (focus_midfreq_config.book_pressure_window() && focus_midfreq_config.book_pressure_const() > 0) {
      book_pressure_ = new twma(focus_midfreq_config.book_pressure_window() * 1'000'000'000LL);
      calc_book_pressure_ = true;
    }

    if (focus_midfreq_config.working_order_window() > 0) {
      LOG(INFO) << fmt::format(
        "make pos_wom, working_order_window: {}", focus_midfreq_config.working_order_window());
      pos_wom_->ResizeWindow(focus_midfreq_config.working_order_window() * 1'000'000'000LL);
      calc_target_pos_ = true;
    }

    LOG(INFO) << fmt::format(
      "abs_signal_lean, signal_window: {}, symbol_len: {}, aggr_lean: {}",
      focus_midfreq_config.signal_window_in_sec(),
      focus_midfreq_config.abs_signal_aggr_lean_bps(),
      focus_midfreq_config.abs_signal_symbol_lean_bps());

    if (focus_midfreq_config.signal_window_in_sec() > 0) {
      signal_ = new sma2(focus_midfreq_config.signal_window_in_sec() * 1'000'000'000LL, 5'000'000'000LL);
      update_signal_ = true;
    }

    stack_queue_ = new StackQueue(focus_midfreq_config);

    midp_avg_ = new twma(focus_midfreq_config.midp_avg_sec() * 1'000'000'000LL);
  }

  LmSubStrategy2(
      const MarketExchangeApi& mea,
      const IProduct* product,
      const Symbol* symbol,
      const LmFocusMidfreqConfig& lm_focus_midfreq_config,
      ProductEncyclopedia* product_cache,
      sma* liquidity,
      sma* market_turnover,
      twma* ask_bbo,
      twma* bid_bbo,
      twma* book_pressure,
      sma* spread,
      sma* tradep,
      sma* tradep2,
      sma2* signal)
      : LmSubStrategy2(mea, product, symbol, lm_focus_midfreq_config, product_cache) {
    if (liquidity != nullptr) {
      liquidity_ = liquidity;
      use_liq_const_ = true;
    }

    if (market_turnover != nullptr) {
     market_turnover_ = market_turnover;
    }

    if (tradep != nullptr && tradep2 != nullptr) {
      tradep_ = tradep;
      tradep2_ = tradep2;
      calc_volatility_ = true;
    }

    if (spread != nullptr) {
      spread_ = spread;
      calc_spread_ = true;
    }

    if (ask_bbo != nullptr && bid_bbo != nullptr) {
      ask_bbo_ = ask_bbo;
      bid_bbo_ = bid_bbo;
    }

    if (book_pressure != nullptr) {
      book_pressure_ = book_pressure;
      calc_book_pressure_ = true;
    }

    if (signal != nullptr) {
      signal_ = signal;
    }
    update_signal_ = false;
  }

  void UpdateLiquidity(int64_t fetched_time, double pq) {
    if (use_liq_const_)
      liquidity_->Update(fetched_time, pq);
  }

  void UpdateSignalWindow(int64_t fetched_time, double signal) {
    if (update_signal_) {
      // use signal, or abs_signal will be defined outside of function
      signal_->Update(fetched_time, signal);
    }
  }

  double GetSignalMean() {
    if (update_signal_) {
      return signal_->average();
    }
    return 0;
  }

  BlockInfo BlockOrder(
      int64_t timestamp,
      OrderExecutorSystem* oe,
      AggregatedSizer2* asizer,
      double* adjust_info,
      const bool clear,
      const double current_stack,
      const LmFocusMidfreqConfig& fconf);

  int ClearPosition(
      int64_t timestamp,
      OrderExecutorSystem* oe,
      BlockInfo* block,
      const double leverage_rate);

  int BboExecution(
    OrderExecutorSystem* oe,
    double* adjust_info,
    const int64_t timestamp,
    const double midret_pred,
    const double sigma,
    const double current_stack,
    const double leverage_rate,
    const BlockInfo& block,
    const LmFocusMidfreqConfig& fconf);

  int MmExecution(
    OrderExecutorSystem* oe,
    double* adjust_info,
    const int64_t timestamp,
    const double midret_pred,
    double sigma,
    const double current_stack,
    const double leverage_rate,
    const BlockInfo& block,
    const LmFocusMidfreqConfig& fconf,
    const double* feats);

  int PortfolioExecution(
    OrderExecutorSystem* oe,
    double* adjust_info,
    const int64_t timestamp,
    const double midret_pred,
    const double* feats,
    const double leverage_rate,
    BlockInfo* block,
    const LmFocusMidfreqConfig& fconf);

  int LmExecution(
    OrderExecutorSystem* oe,
    double* adjust_info,
    const int64_t timestamp,
    const double midret_pred,
    const double sigma,
    const double current_stack,
    const double leverage_rate,
    const BlockInfo& block,
    const LmFocusMidfreqConfig& fconf);

  int TargetPosBboExecution(
    OrderExecutorSystem* oe,
    double* adjust_info,
    const int64_t timestamp,
    const double midret_pred,
    const double sigma,
    const double current_stack,
    const double leverage_rate,
    const BlockInfo& block,
    const LmFocusMidfreqConfig& fconf);

  int UpdateSignal(
      int64_t timestamp,
      int64_t* first_feed_time,
      double midret_pred,
      bool clear,
      double sigma,
      double* feats,
      OrderExecutorSystem* oe,
      // PredictionInterval* pred_interval,
      AggregatedSizer2* asizer,
      double* adjust_info,
      double leverage_rate);

  void onAccountInfo(const OrderUpdate& upd, OrderExecutorSystem* oe) {
    if (!pholder) {
      SPDLOG_INFO("Product Holder not ready.");
      return;
    }
    if (!oe->HasProduct(*pholder)) {
      SPDLOG_INFO("Does not have product: {}", pholder->product().absolute_norm());
      return;
    }
    oe->onAccountInfo(*pholder, upd);
  }
  void onAccountOrder(const OrderUpdate& upd, OrderExecutorSystem* oe) {
    if (!pholder) {
      SPDLOG_INFO("Product Holder not ready.");
      return;
    }
    oe->onAccountOrder(*pholder, upd);
    if (upd.product_order_info().event().type() == coin::proto::OrderEvent::ORDER_FILLED) {
      stack_queue_->UpdateStack(upd.product_order_info().event().event_time(),
          oe->GetConfig(*pholder).qty().max_lot_size());
      UpdateFill(
            oe,
            upd.product_order_info().event().event_time(),
            upd.product_order_info().event().trade_side(),
            upd.product_order_info().event().fill_price(),
            upd.product_order_info().event().fill_qty(),
            upd.product_order_info().event().proc_order_id());
      stack_queue_->UpdateStack(upd.product_order_info().event().event_time(),
          oe->GetConfig(*pholder).qty().max_lot_size());
    }
  }

  void UpdateBbo(const Bbo& bbo) { this->bbo = bbo; }

  void UpdateProduct(ProductEncyclopedia* product_cache, const IProduct& product) {
    CHECK_EQ(mea.exchange, product.exchange());
    if (!pholder) {
      CHECK_EQ(product.absolute_norm(), lholder_.target_product.absolute_norm());
      pholder = &(product_cache->holder(product));
      SPDLOG_INFO("Product Holder is assigned.");
    }
  }

  void UpdateFill(
      OrderExecutorSystem* oe,
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double fill_price,
      double fill_qty,
      int64_t order_id) {
    double lot_size = oe->GetConfig(*pholder).qty().max_lot_size();
    lot_size *= focus_midfreq_config.maker_qty_ratio();
    switch (trade_side) {
      case coin::proto::TRADE_SELL_SIDE:
        bid_fill_qty += fill_qty;
        if (bid_fill_qty >= lot_size) {
          prev_bid_fill_time = timestamp;
          bid_fill_qty = 0;
        }
        sell_turnover_.Update(timestamp, fill_price * fill_qty);
        if (calc_target_pos_) {
          pos_wom_->AddFillOrder(timestamp, order_id, -fill_qty);
        }
        if (use_fill_cooldown_)
          sell_filled_.Update(timestamp, fill_qty);
        break;
      case coin::proto::TRADE_BUY_SIDE:
        ask_fill_qty += fill_qty;
        if (ask_fill_qty >= lot_size) {
          prev_ask_fill_time = timestamp;
          ask_fill_qty = 0;
        }
        buy_turnover_.Update(timestamp, fill_price * fill_qty);
        if (calc_target_pos_) {
          pos_wom_->AddFillOrder(timestamp, order_id, fill_qty);
        }
        if (use_fill_cooldown_)
          buy_filled_.Update(timestamp, fill_qty);
        break;
      default:
        break;
    }
    stack_queue_->Update(timestamp, trade_side, fill_qty);
  }

  void UpdateMidp(StrategyReporter2* strat_reporter) {
    if (!bbo || !pholder) return;
    strat_reporter->UpdateMidp(pholder->product(), 0.5 * (bbo->askp + bbo->bidp));
    lholder_.UpdateMidp(strat_reporter, &pholder->product(), false, true);
  }

  void UpdateTrade(double price,  double qty, int64_t fetched_time) {
    double pq = price * qty;
    market_turnover_->Update(fetched_time, pq);
    UpdateLiquidity(fetched_time, pq);
    if (calc_volatility_) {
      tradep_->Update(fetched_time, price);
      tradep2_->Update(fetched_time, price * price);
    }
  }

  double GetVolatility() {
    if (!calc_volatility_) return 0;
    double mean = tradep_->average();
    return std::sqrt(tradep2_->average() - mean * mean) / mean * 1e4;
  }

  void UpdateBbo2(double ask0_p, double bid0_p, double ask0_pq, double bid0_pq, int64_t fetched_time) {
    ask_bbo_->Update(fetched_time, ask0_pq);
    bid_bbo_->Update(fetched_time, bid0_pq);
    if (calc_book_pressure_) {
      double book_pressure = 0.5;
      if (ask0_pq > 0 && bid0_pq > 0) {
        book_pressure = bid0_pq / (bid0_pq + ask0_pq);
      }
      book_pressure_->Update(fetched_time, book_pressure);
    }
    if (calc_spread_) {
      double spread = 2 * (ask0_p - bid0_p) / (ask0_p + bid0_p);
      spread_->Update(fetched_time, spread);
      DLOG(INFO) << fmt::format(
      "symbol: {}, ask0_p: {}, bid0_p: {}, ask0_pq: {}, bid0_pq: {}, "
      "spread: {}, spread_avg: {}, spread_w_sum: {}, spread_w_num_elem: {}",
       symbol->symbol_string(), ask0_p, bid0_p, ask0_pq, bid0_pq, spread, spread_->average(), spread_->sum(), spread_->num_elements());
    }

    double midp = (ask0_p + bid0_p) / 2.0;
    midp_avg_->Update(fetched_time, midp);
  }

  double GetSpread() {
    if (!calc_spread_) return 0;
    return spread_->average();
  }

  double GetBookPressure() {
    if (!calc_book_pressure_) return 0.5;
    return book_pressure_->average();
  }

  const MarketExchangeApi mea;
  const Symbol* symbol;
  LmFocusMidfreqConfig focus_midfreq_config;
  std::optional<Bbo> bbo;
  const coin2::exchange::base::symbology::IProductHolder* pholder = nullptr;
  LogicHolder lholder_;
  int64_t prev_ask_post_time = 0L;
  int64_t prev_bid_post_time = 0L;
  int64_t prev_ask_fill_time = 0L;
  int64_t prev_bid_fill_time = 0L;
  double bid_fill_qty = 0;
  double ask_fill_qty = 0;
  double signal_aggr_moving_mean = 0;
  BlockOrderMeasures bom = BlockOrderMeasures(0);

 private:
  int64_t last_print_ts_ = 0;
  twma buy_turnover_, sell_turnover_;
  sma* market_turnover_;
  sma* liquidity_;

  // to calculate volatility
  sma* tradep_;
  sma* tradep2_;
  bool calc_volatility_ = false;

  // to calculate spread
  sma* spread_;
  bool calc_spread_ = false;

  twma* ask_bbo_;
  twma* bid_bbo_;
  twma* book_pressure_;
  sma2* signal_;
  bool update_signal_ = false;

  twma sell_filled_, buy_filled_;
  bool use_fill_cooldown_ = false, use_liq_const_ = false;
  bool calc_book_pressure_ = false;
  TargetPosWorkingOrderManager* pos_wom_ = new TargetPosWorkingOrderManager();
  bool calc_target_pos_;

  StackQueue* stack_queue_;

  // for PORTFOLIO_EXEC
  twma* midp_avg_;
  int32_t n_interval_split_ = focus_midfreq_config.n_interval_split();
  int32_t last_interval_split_index_ = -1;
  double interval_pos_ = 0;
  double last_time_remaining_ = -1;
  double last_interval_pos_ = 0;
  double last_target_pos_ = 0;
};

inline double unordered_map_valid_mean(std::unordered_map<const Symbol*, double>& map) {
  double sum = 0;
  double count = 0;
  for (auto&& [symbol, value] : map) {
    (void)symbol;
    if (value > 1e-4) {
        sum += value;
        count += 1;
    }
  }
  if (count > 0) {
    return sum / count;
  } else {
    return 0;
  }
}

// represents 1 symbol
class LmFocusMidfreqLogic : public LinearModelLogic {
 public:
  explicit LmFocusMidfreqLogic(
      const MarketExchangeApi& mea,
      const IProduct& target_product,
      const Symbol* target_symbol,
      const LmProductConfig& lm_product_config,
      ProductEncyclopedia* product_cache,
      const ::presto::SymbolManager* symbol_manager,
      std::shared_ptr<::fastfeature::KlineInfoProto> kline_info = nullptr)
      : LinearModelLogic(mea, target_product, target_symbol, product_cache),
        enable_telemetry_(lm_product_config.enable_telemetry()),
        mea_(mea),
        lm_product_config_(lm_product_config),
        sim_account_id_(lm_product_config.sim_account_id()),
        product_cache_(product_cache),
        // pred_interval_(lm_product_config.focus_midfreq_config().prediction_window_sec()
        //     * 1'000'000'000LL),
        asizer_(lm_product_config.focus_midfreq_config()) {
    symbol_manager->GetAllSymbols(&symbols_);

    // initialize adjust info marker
    adjust_info_path_ =
      lm_product_config_.focus_midfreq_config().adjust_info_path();
    if (!adjust_info_path_.empty())
      std::experimental::filesystem::create_directories(adjust_info_path_);
    adjust_info_.resize(16);

    num_reqs_.ResizeWindow(60'000'000'000LL);
    kline_info_ = kline_info;
  }

  void OpenAdjustInfo(std::string date_str) {
    if (!adjust_info_path_.empty()) {
      std::string adjust_info_path = fmt::format("{}/{}.csv", adjust_info_path_, date_str);
      std::string model_feats_path = fmt::format("{}/{}_feats.csv", adjust_info_path_, date_str);

      // handle duplicated file limit the number of files
      int idx = 1;
      while (idx < 100) {
        if (!std::experimental::filesystem::exists(adjust_info_path.c_str())) {
          break;
        }
        adjust_info_path = fmt::format("{}/{}.{}.csv", adjust_info_path_, date_str, idx);
        idx++;
      }
      idx = 1;
      while (idx < 100) {
        if (!std::experimental::filesystem::exists(model_feats_path.c_str())) {
          break;
        }
        model_feats_path = fmt::format("{}/{}_feats.{}.csv", adjust_info_path_, date_str, idx);
        idx++;
      }

      // openn file
      LOG(INFO) << fmt::format("new adjust info path: {}, model feats path: {}",
          adjust_info_path, model_feats_path);
      adjust_info_writer_ = std::ofstream(adjust_info_path.c_str());
      model_feats_writer_ = std::ofstream(model_feats_path.c_str());

      // 18 columns
      *adjust_info_writer_ << fmt::format(
          "{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
          "symbol", "timestamp", "price", "init_max", "init_min", "lot_size",
          "book_constraint_buy", "book_constraint_sell",
          "current_stack", "aggr_norm_pos", "raw_signal", "lb", "ub",
          "market_turnover", "turnover", "fill_cooldown_buy", "fill_cooldown_sell",
          "max_holding_time");
      last_date_str_ = date_str;
    }
  }

  void CloseAdjustInfo() {
    if (adjust_info_writer_.has_value())
      adjust_info_writer_->close();
    if (model_feats_writer_.has_value())
      model_feats_writer_->close();
  }

  ~LmFocusMidfreqLogic() {
    CloseAdjustInfo();
  }

  void InitImpl(
      ::coin2::app::Driver* driver,
      coin2::exchange::base::strategy_util::StrategyLogger* strat_logger) override {
    strat_logger_ = strat_logger;
    auto curr_time = GetCurrentTimestamp();
    asizer_.Init(curr_time, mea_, symbols_);
    int32_t symbol_idx = 0;
    for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
      (void)name;
      if (oe->mea() == mea_) {
        oe_ = oe.get();
        if (enable_telemetry_) {
          LOG(INFO) << "Set Latency Recorder";
          oe_->order_system()->gateway()->set_latency_recorder(
              strat_logger->mutable_latency_recorder());
        }
        for (auto& [prod, prod_conf] : oe->config().products()) {
          products_.push_back(CreateProductFromNormString(oe->mea(), prod, curr_time));
          if (sim_account_id_ == prod_conf.sim_account_id()) {
            const auto& product = products_.back();
            const Symbol* symbol = product_strategy_->ResolveSymbol(mea_, product->relative_norm());
            if (strats_.count(symbol) == 0) {
              strats_[symbol].reset(new LmSubStrategy2(mea_, product.get(), symbol, lm_product_config_.focus_midfreq_config(), product_cache_));
            }
            CHECK_EQ(strats_.count(symbol), 1) << symbol->symbol_string();
            prodstrats_[GetProductKey(product.get())] = strats_.at(symbol).get();
            configs_.resize(configs_.size() + 1);
            auto& config = configs_.back();
            config.Clear();
            config.set_symbol(products_.back()->absolute_norm());
            config.set_lot_size(prod_conf.qty().max_lot_size());
            config.set_min_pos(prod_conf.qty().min_position());
            config.set_max_pos(prod_conf.qty().max_position());
            focus_symbol_idx[symbol] = symbol_idx;
            symbol_idx++;
          }
        }
      }
    }
    for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
      (void)name;
      if (oe->mea() == mea_) {
        lholder_.InitExecutor(oe.get(), strat_logger, enable_telemetry_, curr_time);
        for (auto&& [symbol, substrat] : strats_) {
          CHECK(lholder_.acc_req.has_value());
          substrat->lholder_.InitAccReq(lholder_.acc_req.value());
          substrat->lholder_.InitExecutor(oe.get(), strat_logger, false, curr_time, true);
        }
      }
    }
    symbol_signal_mean.resize(symbol_idx);
    symbol_signal_mean.BanAll();

    AddKlineInfo();
  }

  void AddKlineInfo() {
    LOG(ERROR) << "AddKlineInfo";
    if (kline_info_ == nullptr) {
      LOG(ERROR) << "kline_info_ is nullptr";
      return;
    }
    for (const auto& kline : kline_info_->klines()) {
      std::string symbol_str = kline.symbol();
      LOG(ERROR) << StringPrintf("add kline info of symbol %s",
          symbol_str.c_str());
      const Symbol* symbol =
        product_strategy_->converter->symbol_manager->FromSymbolString(symbol_str);
      if (!strats_.count(symbol)) continue;
      for (const auto& ohlc : kline.ohlc()) {
        double midp = (ohlc.open() + ohlc.close()) * 0.5;
        strats_[symbol]->UpdateLiquidity(ohlc.kline_timestamp(),
            midp * ohlc.volume());
      }
    }
    kline_info_.reset();
  }

  void OnFill(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double fill_price,
      double fill_qty) final {}

  void onAccountInfo(const OrderUpdate& upd) final {
    if (!oe_) return;
    if (!upd.is_account_info()) return;
    if (upd.sim_account_id && sim_account_id_ != *upd.sim_account_id) return;
    for (auto&& [symbol, substrat] : strats_) {
      (void)symbol;
      substrat->onAccountInfo(upd, oe_);
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    if (!oe_) return;
    if (upd.is_account_info()) return;
    if (upd.sim_account_id && sim_account_id_ != *upd.sim_account_id) return;
    auto pkey = GetProductKey(&upd.product());
    auto it = prodstrats_.find(pkey);
    if (it != prodstrats_.end() && it->second)
      it->second->onAccountOrder(upd, oe_);
  }

  void UpdateMidp(
      StrategyReporter2* strat_reporter,
      const IProduct* target_product_unused) final {
    for (auto&& [symbol, substrat] : strats_) {
      (void)symbol;
      substrat->UpdateMidp(strat_reporter);
    }
  }

  void onFeed(
      const Symbol* symbol,
      const FeedUpdate& upd,
      const ::fastfeature::FeedMessage& feed_msg,
      LinearModel* linear_model,
      int64_t* lm_feed_id,
      const int64_t* global_feed_id,
      LinearModelDelegateProto* delegate,
      bool simulation) override;

 private:
  bool GetIndex(LinearModel* linear_model,
      std::string key, int32_t col_idx) {
    if (!linear_model->GetAllFeatures()
        ->GetIndex(key, &feature_index_[col_idx])) {
      feature_index_[col_idx] = -1;
      return false;
    }
    return true;
  }

  TimeWindowMovingAverage<int> num_reqs_;
  std::vector<std::unique_ptr<IProduct>> products_;
  const bool enable_telemetry_;
  const MarketExchangeApi mea_;
  LmProductConfig lm_product_config_;
  int64_t sim_account_id_;
  int64_t first_feed_time_ = 0L;
  ProductEncyclopedia* product_cache_ = nullptr;
  OrderExecutorSystem* oe_ = nullptr;
  std::unordered_map<const Symbol*, std::unique_ptr<LmSubStrategy2>> strats_;
  std::unordered_map<std::string, LmSubStrategy2*> prodstrats_;
  std::unordered_map<const Symbol*, double> aggr_signal_;

  std::vector<coin::proto::coin_executor::AggressiveExecutorConfig> configs_;
  // PredictionInterval pred_interval_;
  AggregatedSizer2 asizer_;
  std::vector<const Symbol*> symbols_;
  presto::detail::FastSymbolMap<int64_t> last_mark_t_;
  presto::detail::FastSymbolMap<int64_t> last_inference_t_;
  presto::detail::FastSymbolMap<LinearModelDelegateProto> last_inference_delegate_;
  presto::detail::FastSymbolMap<const Symbol*> focus_symbols_;
  int64_t last_signal_mean_update_t_;
  double last_signal_mean = 0;
  std::shared_ptr<::fastfeature::KlineInfoProto> kline_info_;
  presto::detail::FastSymbolMap<int32_t> focus_symbol_idx;
  MeanOnline symbol_signal_mean;

  // adjust info marker
  std::vector<double> adjust_info_;
  std::optional<std::ofstream> adjust_info_writer_;
  std::string adjust_info_path_;
  std::string last_date_str_ = "20000101";

  // model feats marker
  std::vector<double> model_feats_;
  std::vector<std::string> model_feat_keys_;
  std::optional<std::ofstream> model_feats_writer_;
  int num_model_feats_ = 0;

  // to implement mm executor
  std::vector<int> feature_index_;
  std::vector<double> feature_values_;

  // focus does not use logic holder.
  coin2::exchange::base::strategy_util::StrategyLogger* strat_logger_;
};

class SearchLmFocusMidfreqLogic : public LinearModelLogic {
 public:
  explicit SearchLmFocusMidfreqLogic(
      const MarketExchangeApi& mea,
      const IProduct& target_product,
      const Symbol* target_symbol,
      const LmProductConfig& lm_product_config,
      ProductEncyclopedia* product_cache,
      const ::presto::SymbolManager* symbol_manager,
      std::shared_ptr<::fastfeature::KlineInfoProto> kline_info = nullptr)
      : LinearModelLogic(mea, target_product, target_symbol, product_cache),
        enable_telemetry_(lm_product_config.enable_telemetry()),
        mea_(mea),
        lm_product_config_(lm_product_config),
        product_cache_(product_cache) {
        // product_cache_(product_cache) {
        // pred_interval_(lm_product_config.focus_midfreq_config().prediction_window_sec()
        //     * 1'000'000'000LL),
        //) {
    symbol_manager->GetAllSymbols(&symbols_);

    for (const LmFocusMidfreqConfig& focus_midfreq_config : lm_product_config.search_focus_midfreq_config()) {
      sim_account_ids_.push_back(focus_midfreq_config.sim_account_id());
      asizers_.push_back(AggregatedSizer2(focus_midfreq_config));
      num_reqs_.push_back(TimeWindowMovingAverage<int>(60'000'000'000LL));
    }
    last_mark_ts_.resize(sim_account_ids_.size());

    // initialize adjust info marker
    adjust_info_path_ =
      lm_product_config_.search_focus_midfreq_config(0).adjust_info_path();
    if (!adjust_info_path_.empty())
      std::experimental::filesystem::create_directories(adjust_info_path_);
    adjust_info_.resize(16);

    kline_info_ = kline_info;
  }

  void OpenAdjustInfo(std::string date_str) {
    if (!adjust_info_path_.empty()) {
      adjust_info_writers_.clear();
      int idx = 1;
      for (auto sim_account_id_ : sim_account_ids_) {
        std::string adjust_info_path = fmt::format("{}/{}_{}.csv", adjust_info_path_, date_str, sim_account_id_);
        // handle duplicated file limit the number of files
        idx = 1;
        while (idx < 100) {
          if (!std::experimental::filesystem::exists(adjust_info_path.c_str())) {
            break;
          }
          adjust_info_path = fmt::format("{}/{}_{}.{}.csv", adjust_info_path_, date_str, sim_account_id_, idx);
          idx++;
        }
        adjust_info_writers_.push_back(std::ofstream(adjust_info_path.c_str()));
        LOG(INFO) << fmt::format("new adjust info path: {}", adjust_info_path);

        // 18 columns
        *adjust_info_writers_.back() << fmt::format(
            "{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
            "symbol", "timestamp", "price", "init_max", "init_min", "lot_size",
            "book_constraint_buy", "book_constraint_sell",
            "current_stack", "aggr_norm_pos", "raw_signal", "lb", "ub",
            "market_turnover", "turnover", "fill_cooldown_buy", "fill_cooldown_sell",
            "max_holding_time_in_sec");
      }
      std::string model_feats_path = fmt::format("{}/{}_feats.csv", adjust_info_path_, date_str);
      idx = 1;
      while (idx < 100) {
        if (!std::experimental::filesystem::exists(model_feats_path.c_str())) {
          break;
        }
        model_feats_path = fmt::format("{}/{}_feats.{}.csv", adjust_info_path_, date_str, idx);
        idx++;
      }
      LOG(INFO) << fmt::format("new model feats path: {}", model_feats_path);
      model_feats_writer_ = std::ofstream(model_feats_path.c_str());

      last_date_str_ = date_str;
    }
  }

  void CloseAdjustInfo() {
    for (int i = 0; i < adjust_info_writers_.size(); i++) {
      if (adjust_info_writers_[i].has_value())
        adjust_info_writers_[i]->close();
    }
    if (model_feats_writer_.has_value())
      model_feats_writer_->close();
  }

  ~SearchLmFocusMidfreqLogic() {
    CloseAdjustInfo();
  }

  void AssignMovingWindow(
      const Symbol* symbol,
      const LmFocusMidfreqConfig& focus_midfreq_config) {

    if (market_turnover_.count(symbol) == 0) {
       market_turnover_[symbol] = new sma(
           focus_midfreq_config.pr_window_in_sec() * 1'000'000'000LL,
           1'000'000'000LL);
    }

    if (liquidity_.count(symbol) == 0 &&
        focus_midfreq_config.liquidity_constraint() > 0) {
      liquidity_[symbol] = new sma(
          3600LL * 24LL * 1'000'000'000LL, 60'000'000'000LL);
    }
    if (tradep_.count(symbol) == 0 &&
        focus_midfreq_config.volat_window() > 0 &&
        (focus_midfreq_config.volat_const() > 0 || focus_midfreq_config.volatility_const() > 0)) {
      tradep_[symbol] = new sma(
          focus_midfreq_config.volat_window() * 1'000'000'000LL,
          1'000'000'000LL);
      tradep2_[symbol] = new sma(
          focus_midfreq_config.volat_window() * 1'000'000'000LL,
          1'000'000'000LL);
    }

    if (spread_.count(symbol) == 0 &&
        focus_midfreq_config.spread_window() > 0 &&
        focus_midfreq_config.spread_const() > 0) {
      spread_[symbol] = new sma(
          focus_midfreq_config.spread_window() * 1'000'000'000LL,
          1'000'000'000LL);
    }

    if (ask_bbo_.count(symbol) == 0) {
      ask_bbo_[symbol] = new twma(
          focus_midfreq_config.book_window_in_sec() * 1'000'000'000LL);
      bid_bbo_[symbol] = new twma(
          focus_midfreq_config.book_window_in_sec() * 1'000'000'000LL);
    }
    if (book_pressure_.count(symbol) == 0 &&
        focus_midfreq_config.book_pressure_window() &&
        focus_midfreq_config.book_pressure_const() > 0) {
      book_pressure_[symbol] = new twma(
          focus_midfreq_config.book_pressure_window() * 1'000'000'000LL);
    }
    if (signal_.count(symbol) == 0 && focus_midfreq_config.signal_window_in_sec() > 0) {
      signal_[symbol] = new sma2(
          focus_midfreq_config.signal_window_in_sec() * 1'000'000'000LL, 5'000'000'000LL);
    }
  }

  void CheckSearchConfig() {
    int liquidity_nums = 0, tradep_nums = 0, spread_nums = 0, book_pressure_nums = 0;
    for (int i = 0; i < sim_account_ids_.size(); i++) {
      const auto& focus_midfreq_config = lm_product_config_.search_focus_midfreq_config(i);
      if (focus_midfreq_config.liquidity_constraint() > 0) {
        liquidity_nums++;
      }
      if (focus_midfreq_config.volat_window() > 0 &&
          (focus_midfreq_config.volat_const() > 0 || focus_midfreq_config.volatility_const() > 0)) {
        tradep_nums++;
      }
      if (focus_midfreq_config.spread_window() > 0 &&
          focus_midfreq_config.spread_const() > 0) {
        spread_nums++;
      }
      if (focus_midfreq_config.book_pressure_window() &&
          focus_midfreq_config.book_pressure_const() > 0) {
        book_pressure_nums++;
      }
    }
    CHECK_EQ(liquidity_nums % sim_account_ids_.size(), 0)
      << "invalid liquidity constraint";
    CHECK_EQ(tradep_nums % sim_account_ids_.size(), 0)
      << "invalid volatility constraint";
    CHECK_EQ(spread_nums % sim_account_ids_.size(), 0)
      << "invalid spread constraint";
    CHECK_EQ(book_pressure_nums % sim_account_ids_.size(), 0)
      << "invalid book_pressure constraint";
  }

  void InitImpl(
      ::coin2::app::Driver* driver,
      coin2::exchange::base::strategy_util::StrategyLogger* strat_logger) override {
    strat_logger_ = strat_logger;
    auto curr_time = GetCurrentTimestamp();
    for (auto& asizer_ : asizers_) {
      asizer_.Init(curr_time, mea_, symbols_);
    }
    CheckSearchConfig();
    int32_t symbol_idx = 0;
    for (int i = 0; i < sim_account_ids_.size(); i++) {
      auto sim_account_id_ = sim_account_ids_[i];
      for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
        (void)name;
        if (oe->mea() == mea_) {
          oes_.push_back(oe.get());
          auto oe_ = oes_.back();
          if (enable_telemetry_) {
            LOG(INFO) << "Set Latency Recorder";
            oe_->order_system()->gateway()->set_latency_recorder(
                strat_logger->mutable_latency_recorder());
          }
          for (auto& [prod, prod_conf] : oe->config().products()) {
            products_.push_back(CreateProductFromNormString(oe->mea(), prod, curr_time));
            if (sim_account_id_ == prod_conf.sim_account_id()) {
              const auto& product = products_.back();
              const auto& symbols = lm_product_config_.search_focus_midfreq_config(i).symbols();
              if (std::find(symbols.begin(), symbols.end(), product->relative_norm()) == symbols.end()) {
                continue;
              }
              const Symbol* symbol = product_strategy_->ResolveSymbol(mea_, product->relative_norm());
              AssignMovingWindow(symbol, lm_product_config_.search_focus_midfreq_config(i));
              if (strats_.count(symbol) == 0 || strats_[symbol].count(sim_account_id_) == 0) {

                auto shared_liquidity = GetSharedMW(symbol, liquidity_);
                auto shared_market_turnover = GetSharedMW(symbol, market_turnover_);
                auto shared_ask_bbo = GetSharedMW(symbol, ask_bbo_);
                auto shared_bid_bbo = GetSharedMW(symbol, bid_bbo_);
                auto shared_book_pressure = GetSharedMW(symbol, book_pressure_);
                auto shared_spread = GetSharedMW(symbol, spread_);
                auto shared_tradep = GetSharedMW(symbol, tradep_);
                auto shared_tradep2 = GetSharedMW(symbol, tradep2_);
                auto shared_signal = GetSharedMW(symbol, signal_);

                strats_[symbol][sim_account_id_].reset(
                    new LmSubStrategy2(mea_, product.get(), symbol,
                      lm_product_config_.search_focus_midfreq_config(i), product_cache_,
                      shared_liquidity, shared_market_turnover,
                      shared_ask_bbo, shared_bid_bbo, shared_book_pressure,
                      shared_spread, shared_tradep, shared_tradep2, shared_signal)
                );
              }
              CHECK_EQ(strats_.count(symbol), 1) << symbol->symbol_string();
              prodstrats_[GetProductKey(product.get())][sim_account_id_] = strats_.at(symbol).at(sim_account_id_).get();
              configs_.resize(configs_.size() + 1);
              auto& config = configs_.back();
              config.Clear();
              config.set_symbol(products_.back()->absolute_norm());
              config.set_lot_size(prod_conf.qty().max_lot_size());
              config.set_min_pos(prod_conf.qty().min_position());
              config.set_max_pos(prod_conf.qty().max_position());
              if (focus_symbol_idx.count(symbol) == 0) {
                focus_symbol_idx[symbol] = symbol_idx;
                symbol_idx++;
              }
            }
          }
        }
      }

      for (auto& [name, oe] : driver->strategy()->order_executors(sim_account_id_)) {
        (void)name;
        if (oe->mea() == mea_) {
          lholder_.InitExecutor(oe.get(), strat_logger, enable_telemetry_, curr_time);
          CHECK(lholder_.acc_req.has_value());
          for (auto&& [symbol, substrat] : strats_) {
            (void)symbol;
            for (auto&& [sim_account_id, strat] : substrat) {
              if (sim_account_id != sim_account_id_) {
                continue;
              }
              strat->lholder_.InitAccReq(lholder_.acc_req.value());
              strat->lholder_.InitExecutor(oe.get(), strat_logger, false, curr_time, true);
            }
          }
        }
      }
    }
    symbol_signal_mean.resize(symbol_idx);
    symbol_signal_mean.BanAll();

    AddKlineInfo();
  }

  void UpdateLiquidity(const Symbol* symbol, int64_t fetched_time, double pq) {
    if (liquidity_.count(symbol) > 0) {
      liquidity_[symbol]->Update(fetched_time, pq);
    }
  }

  void AddKlineInfo() {
    LOG(ERROR) << "AddKlineInfo";
    if (kline_info_ == nullptr) {
      LOG(ERROR) << "kline_info_ is nullptr";
      return;
    }
    for (const auto& kline : kline_info_->klines()) {
      std::string symbol_str = kline.symbol();
      LOG(ERROR) << StringPrintf("add kline info of symbol %s",
          symbol_str.c_str());
      const Symbol* symbol =
        product_strategy_->converter->symbol_manager->FromSymbolString(symbol_str);
      if (!strats_.count(symbol)) continue;
      for (const auto& ohlc : kline.ohlc()) {
        double midp = (ohlc.open() + ohlc.close()) * 0.5;
        UpdateLiquidity(symbol, ohlc.kline_timestamp(), midp * ohlc.volume());
      }
    }
    kline_info_.reset();
  }

  void OnFill(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double fill_price,
      double fill_qty) final {}

  void onAccountInfo(const OrderUpdate& upd) final {
    if (oes_.empty()) return;
    if (!upd.is_account_info()) return;
    if (!upd.sim_account_id) return;
    for (int i = 0; i < sim_account_ids_.size(); i++) {
      auto sim_account_id_ = sim_account_ids_[i];
      if (sim_account_id_ == *upd.sim_account_id) {
        for (auto&& [symbol, substrat] : strats_) {
          (void)symbol;
          if (substrat.find(sim_account_id_) != substrat.end())
            substrat[sim_account_id_]->onAccountInfo(upd, oes_[i]);
        }
        break;
      }
    }
  }

  void onAccountOrder(const OrderUpdate& upd) final {
    if (oes_.empty()) return;
    if (upd.is_account_info()) return;
    if (!upd.sim_account_id) return;
    for (int i = 0; i < sim_account_ids_.size(); i++) {
      auto sim_account_id_ = sim_account_ids_[i];
      if (sim_account_id_ == *upd.sim_account_id) {
        auto pkey = GetProductKey(&upd.product());
        auto it = prodstrats_.find(pkey);
        if (it != prodstrats_.end() && it->second.count(sim_account_id_) > 0)
          it->second.at(sim_account_id_)->onAccountOrder(upd, oes_[i]);
        break;
      }
    }
  }

  void UpdateMidp(
      StrategyReporter2* strat_reporter,
      const IProduct* target_product_unused) final {
    for (auto&& [symbol, substrat] : strats_) {
      (void)symbol;
      for (auto&& [sim_account_id_, strat] : substrat) {
        (void)sim_account_id_;
        strat->UpdateMidp(strat_reporter);
        break;
      }
    }
  }

  void onFeed(
      const Symbol* symbol,
      const FeedUpdate& upd,
      const ::fastfeature::FeedMessage& feed_msg,
      LinearModel* linear_model,
      int64_t* lm_feed_id,
      const int64_t* global_feed_id,
      LinearModelDelegateProto* delegate,
      bool simulation) override;

 private:
  bool GetIndex(LinearModel* linear_model,
      std::string key, int32_t col_idx) {
    if (!linear_model->GetAllFeatures()
        ->GetIndex(key, &feature_index_[col_idx])) {
      feature_index_[col_idx] = -1;
      return false;
    }
    return true;
  }

  template<typename T>
  T* GetSharedMW(const Symbol* symbol,
      std::unordered_map<const Symbol*, T*> shared_mw) {
    if (shared_mw.count(symbol) > 0) {
      return shared_mw[symbol];
    }
    return nullptr;
  }

  typedef SampledMovingAverage<double> sma;
  typedef SampledMovingAverage2<double> sma2;
  typedef TimeWindowMovingAverage<double> twma;

  std::vector<TimeWindowMovingAverage<int>> num_reqs_;
  std::vector<std::unique_ptr<IProduct>> products_;
  const bool enable_telemetry_;
  const MarketExchangeApi mea_;
  LmProductConfig lm_product_config_;
  std::vector<int64_t> sim_account_ids_;
  int64_t first_feed_time_ = 0L;
  ProductEncyclopedia* product_cache_ = nullptr;
  std::vector<OrderExecutorSystem*> oes_;
  std::unordered_map<const Symbol*, std::unordered_map<int64_t, std::unique_ptr<LmSubStrategy2>>> strats_;
  std::unordered_map<std::string, std::unordered_map<int64_t, LmSubStrategy2*>> prodstrats_;
  std::vector<coin::proto::coin_executor::AggressiveExecutorConfig> configs_;
  // PredictionInterval pred_interval_;
  std::vector<AggregatedSizer2> asizers_;
  std::vector<const Symbol*> symbols_;
  std::vector<presto::detail::FastSymbolMap<int64_t>> last_mark_ts_;
  presto::detail::FastSymbolMap<int64_t> last_mark_feature_t_;
  presto::detail::FastSymbolMap<int64_t> last_inference_t_;
  presto::detail::FastSymbolMap<LinearModelDelegateProto> last_inference_delegate_;
  presto::detail::FastSymbolMap<const Symbol*> focus_symbols_;
  int64_t last_signal_mean_update_t_;
  double last_signal_mean = 0;
  std::shared_ptr<::fastfeature::KlineInfoProto> kline_info_;
  presto::detail::FastSymbolMap<int32_t> focus_symbol_idx;
  MeanOnline symbol_signal_mean;

  // adjust info marker
  std::vector<double> adjust_info_;
  std::vector<std::optional<std::ofstream>> adjust_info_writers_;
  std::string adjust_info_path_;
  std::string last_date_str_ = "20000101";

  // model feats marker
  std::vector<double> model_feats_;
  std::vector<std::string> model_feat_keys_;
  std::optional<std::ofstream> model_feats_writer_;
  int num_model_feats_ = 0;

  // to implement mm executor
  std::vector<int> feature_index_;
  std::vector<double> feature_values_;

  // to gather moving average
  std::unordered_map<const Symbol*, sma*> liquidity_;
  std::unordered_map<const Symbol*, sma*> market_turnover_;
  std::unordered_map<const Symbol*, twma*> ask_bbo_;
  std::unordered_map<const Symbol*, twma*> bid_bbo_;
  std::unordered_map<const Symbol*, twma*> book_pressure_;
  std::unordered_map<const Symbol*, sma*> spread_;
  std::unordered_map<const Symbol*, sma*> tradep_;
  std::unordered_map<const Symbol*, sma*> tradep2_;
  std::unordered_map<const Symbol*, sma2*> signal_;
  std::unordered_map<const Symbol*, double> aggr_signal_;

  // focus does not use logic holder.
  coin2::exchange::base::strategy_util::StrategyLogger* strat_logger_;
};

}  // namespace coin2::strategy::linear_model
